package de.steric.battlemon;

import java.util.ArrayList;

import org.lwjgl.util.vector.Matrix3f;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;

import de.steric.MathHelper;
import de.steric.battlemon.collision.Collider;
import de.steric.battlemon.rendering.Graphics;
import de.steric.battlemon.rendering.Renderer;

public class GameObject {
	private Vector3f position;
	private Vector3f localPosition;
	private Quaternion rotation, localRotation;
	private GameObject parent;
	private Vector3f scale, localScale;
	private Matrix4f localTransform, transform;
	public Collider collider;
	private ArrayList<GameObject> children;
	public String name;
	public Renderer renderer;
	public boolean shallRender;
	private boolean isDirty;

	public Matrix4f getLocalTransform() {
		return localTransform;
	}

	public Matrix4f getTransform() {
		return transform;
	}

	public Quaternion getRotation() {
		if (isDirty)
			updateTransform();
		return rotation;
	}

	public Quaternion getLocalRotation() {
		return localRotation;
	}

	public Vector3f getScale() {
		if (isDirty)
			updateTransform();
		return scale;
	}

	public Vector3f getLocalScale() {
		return localScale;
	}

	public Vector3f getPosition() {
		if (isDirty)
			updateTransform();
		return position;
	}

	public Vector3f getLocalPosition() {
		return localPosition;
	}

	public void setLocalPosition(Vector3f posN) {
		localTransform.m30 = posN.x;
		localTransform.m31 = posN.y;
		localTransform.m32 = posN.z;

		localPosition = posN;
		setDirty();
	}

	protected void setDirty() {
		if (isDirty)
			return;
		isDirty = true;
		for (GameObject g : children) {
			g.setDirty();
		}
	}

	/**
	 * Updates transformation and cleans all children.
	 * 
	 * Parent must be clean for this!
	 */
	public void cleanAll() {
		if (parent.isDirty) {
			System.err.println("Cannot clean, if parent is dirty");
			return;
		}
		if (!isDirty)
			return;

		Matrix4f.mul(localTransform, parent.transform, transform);
		position = new Vector3f(transform.m30, transform.m31, transform.m32);
		Quaternion.mul(localRotation, parent.rotation, rotation);
		Matrix3f rot = MathHelper.shrink(MathHelper.getRotation(rotation));
		rot.invert();
		Matrix3f scaleMat = Matrix3f.mul(rot, MathHelper.shrink(transform),
				null);
		scale = new Vector3f(scaleMat.m00, scaleMat.m11, scaleMat.m22);
		isDirty = false;
		for (GameObject g : children) {
			g.cleanAll();
		}
	}

	protected void updateTransform() {
		if (!isDirty)
			return;
		parent.updateTransform();
		Matrix4f.mul(localTransform, parent.transform, transform);
		position = new Vector3f(transform.m30, transform.m31, transform.m32);
		Quaternion.mul(localRotation, parent.rotation, rotation);
		Matrix3f rot = MathHelper.shrink(MathHelper.getRotation(rotation));
		rot.invert();
		Matrix3f scaleMat = Matrix3f.mul(rot, MathHelper.shrink(transform),
				null);
		scale = new Vector3f(scaleMat.m00, scaleMat.m11, scaleMat.m22);
		isDirty = false;
	}

	public void setLocalRotation(Quaternion rotN) {
		Matrix4f newRot = MathHelper.getRotation(rotN);
		Matrix4f scale = new Matrix4f();
		Matrix4f.scale(localScale, scale, scale);
		Matrix4f.mul(newRot, scale, localTransform);
		localTransform.m30 = localPosition.x;
		localTransform.m31 = localPosition.y;
		localTransform.m32 = localPosition.z;

		localRotation = rotN;
		setDirty();
	}

	public void setLocalScale(Vector3f scaleN) {
		Matrix4f newScale = new Matrix4f();
		newScale.m00 = scaleN.x;
		newScale.m11 = scaleN.y;
		newScale.m22 = scaleN.z;
		Matrix4f rot = MathHelper.getRotation(localRotation);
		Matrix4f.mul(rot, newScale, localTransform);
		localTransform.m30 = localPosition.x;
		localTransform.m31 = localPosition.y;
		localTransform.m32 = localPosition.z;

		localScale = scaleN;
		setDirty();
	}

	public GameObject getParent() {
		return parent;
	}

	public ArrayList<GameObject> getChildren() {
		return children;
	}

	public GameObject() {
		// position = new Vector3f(0, 0, 0);
		localPosition = new Vector3f();
		localRotation = new Quaternion();
		scale = new Vector3f();
		localScale = new Vector3f();
		rotation = new Quaternion();
		children = new ArrayList<>();
		shallRender = true;
		isDirty = false;
		localTransform = new Matrix4f();
	}

	// wird vom graphic thread benutzt
	public final void _render() {
		if(!shallRender)
			return;
		if (renderer != null)
			render();
		for (GameObject o : children) {
			o._render();
		}
	}
	protected void render()
	{
		renderer.render(this,Graphics.graphics.camera);
	}
	public GameObject(String n) {
		name = n;
		position = new Vector3f(0, 0, 0);
		rotation = new Quaternion();
		children = new ArrayList<>();
		shallRender = true;
	}

	// @return if added, return true, if already child or self gameObject false
	public boolean addChild(GameObject other) {
		if (children.contains(other) || this == other) {
			return false;
		}
		children.add(other);
		other.parent = this;
		return true;
	}

	// @return if added, return true, if already child or self gameObject false
	public boolean setParent(GameObject other) {
		if (other.children.contains(this) || this == other) {
			return false;
		}
		other.children.add(this);
		parent = other;
		return true;
	}

	public void addChildren(ArrayList<ArrayList> allArrays) {
		if (!this.children.isEmpty()) {
			allArrays.add(this.children);
			for (GameObject element : children) {
				element.addChildren(allArrays);
			}
		}
	}

}
